package com.jinke.api.common.annotation;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jinke.api.common.exception.ApiException;
import com.jinke.api.common.exception.Asserts;
import com.jinke.api.modules.base.constant.RedisKeyConstants;
import com.jinke.api.modules.third.ezeeship.model.request.EstimateRateRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @projectName:
 * @title: OrderApiIdempotenceHandler
 * @description: 订单幂等实现
 * @see:
 * @author: hhp
 * @version: 1.0.0
 * @createTime: 2024/8/6 15:06
 */
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class OrderApiIdempotenceHandler {

    private final StringRedisTemplate stringRedisTemplate;

    /**
     * @Author: hhp
     * @Description: 定义切面 这里采用的是注解路径，也就是所有胶乳这个注解的方法都会被切面进行管理
     * @Data: 2023/10/23 16:03
     * @param
     * @return: void
     * @throws:
     **/
    @Pointcut("@annotation(com.jinke.api.common.annotation.OrderApiIdempotence)")
    public void orderApiIdempotenceHandler() {

    }

    /**
     * @Author: hhp
     * @Description: 在切面逻辑之前执行这个方法
     * @Data: 2023/10/23 16:05
     * @param
     * @return: void
     * @throws:
     **/
    public void doBefore() throws Throwable {
        log.info("orderApiIdempotenceHandler...doBefore()");
    }

    @Around("orderApiIdempotenceHandler()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 拿到httpServletRequest
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        // 从request中拿到请求头 作为token
        String authorization = request.getHeader("Authorization");

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        Method method = methodSignature.getMethod();

        // 只要一个在实体类里没有就报错
        /*TypeReference<EstimateRateRequest> typeReference = new TypeReference<EstimateRateRequest>() {};
        EstimateRateRequest estimateRateRequest = convertRequestParamsToEntity(request, typeReference);*/
        // 使用fastjson转
        String body = getPostRequestBody(request);
        EstimateRateRequest estimateRateRequest = JSONObject.parseObject(body, EstimateRateRequest.class);
        log.info("orderNo:{}, estimateRateRequest2:{}", estimateRateRequest.getOrderNO(), estimateRateRequest.toString());
        //获取自定义注解值
        OrderApiIdempotence orderApiIdempotence = methodSignature.getMethod().getDeclaredAnnotation(OrderApiIdempotence.class);
        // 过期时间
        long expireTime = orderApiIdempotence.expireTime();

        if (validToken(authorization, expireTime, estimateRateRequest)) {
            return joinPoint.proceed();
        } else {
            //Asserts.fail(expireTime + "秒内, 接口超过可调次数");
            throw new ApiException("同一订单请等"+expireTime + "秒后再提交");
        }
    }

    private boolean validToken(String authorization, long expTime, EstimateRateRequest estimateRateRequest) {
        log.info("幂等校验,orderNo:{}", estimateRateRequest.getOrderNO());
        //new ObjectMapper().readValue(userResourcesStr, new TypeReference<List<UserResource>>(){})
        // 设置 lua脚本 其中keys[1] 是key, keys[2]是value
        String script = "if redis.call('get', KEYS[1]) == KEYS[2] then return 1 else return 0 end";
        // 找到key并且和值相等 返回1  否则0
        RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        // 根据key前缀拼接key
        String key = RedisKeyConstants.IDEMPOTENT_TOKEN_ORDER_PREFIX + authorization + estimateRateRequest.getOrderNO();
        //执行lua脚本
        Long result = stringRedisTemplate.execute(redisScript, Arrays.asList(key, estimateRateRequest.getOrderNO()));
        //根据返回结果判断是否成功匹配并删除 redis 键值对， 如果结果不为空和0 则验证通过
        if (result != null) {
            if (result == 0L) {
                log.info("第一次调 设置值和过期时间");
                stringRedisTemplate.opsForValue().set(key, estimateRateRequest.getOrderNO(), expTime, TimeUnit.SECONDS);
                log.info("expTime:[{}]秒内, 验证key:[{}], value:[{}] orderNo:[{}] 成功", expTime, key, authorization, estimateRateRequest.getOrderNO());
                return true;
            } else {
                log.info("expTime:[{}]秒内, 验证key:[{}], value:[{}] orderNo:[{}] 失败", expTime, key, authorization, estimateRateRequest.getOrderNO());
                return false;
            }
        }
        log.info("expTime:[{}]秒内, 验证key:[{}], value:[{}] orderNo:[{}] 失败", expTime, key, authorization, estimateRateRequest.getOrderNO());
        return false;
    }

    /*private Object returnObject(Method method) throws IllegalAccessException, InstantiationException {
        Class<?> returnType = method.getReturnType();
        return JSON.parseObject(JSONObject.toJSONString(Result.failed(ResultCode.INTERFACE_IDEMPOTENCE)), returnType);
    }*/

    @AfterReturning(returning = "retVal", pointcut = "orderApiIdempotenceHandler()")
    public void doAfter(Object retVal) throws Throwable {
        log.debug("{}", retVal);
    }

    public static <T> T convertRequestParamsToEntity(HttpServletRequest request, TypeReference<T> typeReference) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader reader = request.getReader();
        String line;
        while ((line = reader.readLine())!= null) {
            stringBuilder.append(line);
        }
        String json = stringBuilder.toString();

        ObjectMapper objectMapper = new ObjectMapper();
        return objectMapper.readValue(json, typeReference);
    }

    public static String getPostRequestBody(HttpServletRequest request) {
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader reader = null;
        try {
            reader = request.getReader();
            String line;
            while ((line = reader.readLine())!= null) {
                stringBuilder.append(line);
            }
        } catch (IOException e) {
            log.error("生成面单读取请求参数失败!, errorMsg:{}", e.getMessage());
            Asserts.fail("生成面单读取请求参数失败!");
        } finally {
            if (ObjectUtil.isNotNull(reader)) {
                try {
                    reader.close();
                } catch (IOException e) {
                    log.error("生成面单读取请求参数关闭流失败!, errorMsg:{}", e.getMessage());
                    Asserts.fail("生成面单读取请求参数后关闭读取流失败!");
                }
            }
        }
        return stringBuilder.toString();
    }
}
